<!DOCTYPE html>
<meta charset=utf-8>
<title>Test for handling of logical and physical properties</title>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script src="/tests/SimpleTest/SimpleTest.js"></script>

<style id="sheet"></style>

<!-- specify size for <body> to avoid unconstrained-isize warnings
     when writing-mode of the test <div> is vertical-* -->
<body style="width:100px; height: 100px;">
  <div id="test" class="test"></div>
</body>

<script>
var gSheet = document.getElementById("sheet");
var gTest  = document.getElementById("test");

// list of groups of physical and logical box properties, such as
//
// { left: "margin-left", right: "margin-right",
//   top: "margin-top", bottom: "margin-bottom",
//   inlineStart: "margin-inline-start", inlineEnd: "margin-inline-end",
//   blockStart: "margin-block-start", blockEnd: "margin-block-end",
//   type: "length", prerequisites: "..." }
//
// where the type is a key from the gValues object and the prerequisites
// is a declaration including gCSSProperties' listed prerequisites for
// all four physical properties.
var gBoxPropertyGroups;

// list of groups of physical and logical axis properties, such as
//
// { horizontal: "width", vertical: "height",
//   inline: "inline-size", block: "block-size",
//   type: "length", prerequisites: "..." }
var gAxisPropertyGroups;

// values to use while testing
var gValues = {
  "length":       ["1px", "2px", "3px", "4px", "5px"],
  "color":        ["rgb(1, 1, 1)", "rgb(2, 2, 2)", "rgb(3, 3, 3)", "rgb(4, 4, 4)", "rgb(5, 5, 5)"],
  "border-style": ["solid", "dashed", "dotted", "double", "groove"],
};

// Six unique overall writing modes for property-mapping purposes.
// Note that text-orientation does not affect these mappings, now that
// the proposed sideways-left value no longer exists (superseded in CSS
// Writing Modes by writing-mode: sideways-lr).
var gWritingModes = [
  { style: [
      "writing-mode: horizontal-tb; direction: ltr; ",
    ],
    blockStart: "top", blockEnd: "bottom", inlineStart: "left", inlineEnd: "right",
    block: "vertical", inline: "horizontal" },
  { style: [
      "writing-mode: horizontal-tb; direction: rtl; ",
    ],
    blockStart: "top", blockEnd: "bottom", inlineStart: "right", inlineEnd: "left",
    block: "vertical", inline: "horizontal" },
  { style: [
      "writing-mode: vertical-rl; direction: rtl; ",
      "writing-mode: sideways-rl; direction: rtl; ",
    ],
    blockStart: "right", blockEnd: "left", inlineStart: "bottom", inlineEnd: "top",
    block: "horizontal", inline: "vertical" },
  { style: [
      "writing-mode: vertical-rl; direction: ltr; ",
      "writing-mode: sideways-rl; direction: ltr; ",
    ],
    blockStart: "right", blockEnd: "left", inlineStart: "top", inlineEnd: "bottom",
    block: "horizontal", inline: "vertical" },
  { style: [
      "writing-mode: vertical-lr; direction: rtl; ",
      "writing-mode: sideways-lr; direction: ltr; ",
    ],
    blockStart: "left", blockEnd: "right", inlineStart: "bottom", inlineEnd: "top",
    block: "horizontal", inline: "vertical" },
  { style: [
      "writing-mode: vertical-lr; direction: ltr; ",
      "writing-mode: sideways-lr; direction: rtl; ",
    ],
    blockStart: "left", blockEnd: "right", inlineStart: "top", inlineEnd: "bottom",
    block: "horizontal", inline: "vertical" },
];

function init() {
  gBoxPropertyGroups = [];

  for (var p in gCSSProperties) {
    var type = gCSSProperties[p].type;

    if ((type == CSS_TYPE_SHORTHAND_AND_LONGHAND ||
         type == CSS_TYPE_LONGHAND && gCSSProperties[p].logical) &&
        /-inline-end/.test(p)) {
      var valueType;
      if (/margin|padding|width|offset/.test(p)) {
        valueType = "length";
      } else if (/color/.test(p)) {
        valueType = "color";
      } else if (/border.*style/.test(p)) {
        valueType = "border-style";
      } else {
        throw `unexpected property ${p}`;
      }
      var group = {
        inlineStart: p.replace("-inline-end", "-inline-start"),
        inlineEnd:   p,
        blockStart:  p.replace("-inline-end", "-block-start"),
        blockEnd:    p.replace("-inline-end", "-block-end"),
        type:        valueType
      };
      if (/^offset/.test(p)) {
        group.left   = "left";
        group.right  = "right";
        group.top    = "top";
        group.bottom = "bottom";
      } else {
        group.left   = p.replace("-inline-end", "-left");
        group.right  = p.replace("-inline-end", "-right");
        group.top    = p.replace("-inline-end", "-top");
        group.bottom = p.replace("-inline-end", "-bottom");
      }
      group.prerequisites =
        make_declaration(gCSSProperties[group.top].prerequisites) +
        make_declaration(gCSSProperties[group.right].prerequisites) +
        make_declaration(gCSSProperties[group.bottom].prerequisites) +
        make_declaration(gCSSProperties[group.left].prerequisites);
      gBoxPropertyGroups.push(group);
    }
  }

  // We don't populate this automatically since the only entries we have, for
  // inline-size etc., don't lend themselves to automatically determining
  // the names "width", "height", "min-width", etc.
  gAxisPropertyGroups = [];
  ["", "max-", "min-"].forEach(function(aPrefix) {
    gAxisPropertyGroups.push({
      horizontal: `${aPrefix}width`, vertical: `${aPrefix}height`,
      inline: `${aPrefix}inline-size`, block: `${aPrefix}block-size`,
      type: "length",
      prerequisites:
        make_declaration(gCSSProperties[`${aPrefix}height`].prerequisites)
    });
  });
}

function test_computed_values(aTestName, aRules, aExpectedValues) {
  gSheet.textContent = aRules;
  var cs = getComputedStyle(gTest);
  aExpectedValues.forEach(function(aPair) {
    is(cs.getPropertyValue(aPair[0]), aPair[1], `${aTestName}, ${aPair[0]}`);
  });
  gSheet.textContent = "";
}

function make_declaration(aObject) {
  var decl = "";
  if (aObject) {
    for (var p in aObject) {
      decl += `${p}: ${aObject[p]}; `;
    }
  }
  return decl;
}

function start() {
  var script = document.createElement("script");
  script.src = "property_database.js";
  script.onload = function() {
    init();
    run_tests();
  };
  document.body.appendChild(script);
}

function run_axis_test_for_writing_mode(aGroup, aWritingMode, aWritingModeDecl) {
  var values = gValues[aGroup.type];
  var decl;

  // Test that logical axis properties are converted to their physical
  // equivalent correctly when all four are present on a single
  // declaration, with no overwriting of previous properties and
  // no physical properties present.  We put the writing mode properties
  // on a separate declaration to test that the computed values of these
  // properties are used, rather than those on the same declaration.

  decl = aGroup.prerequisites +
         `${aGroup.inline}: ${values[0]}; ` +
         `${aGroup.block}: ${values[1]}; `;
  test_computed_values('logical properties on one declaration, writing ' +
                         'mode properties on another, ' +
                         `'${aWritingModeDecl}'`,
                       `.test { ${aWritingModeDecl} } ` +
                       `.test { ${decl} }`,
                       [[aGroup[aWritingMode.inline], values[0]],
                        [aGroup[aWritingMode.block],  values[1]]]);


  // Test that logical and physical axis properties are cascaded together,
  // honoring their relative order on a single declaration.

  // (a) with a single logical property after the physical ones

  ["inline", "block"].forEach(function(aLogicalAxis) {
    decl = aWritingModeDecl + aGroup.prerequisites +
           `${aGroup.horizontal}: ${values[0]}; ` +
           `${aGroup.vertical}: ${values[1]}; ` +
           `${aGroup[aLogicalAxis]}: ${values[2]}; `;
    var expected = ["horizontal", "vertical"].map(
      (axis, i) => [aGroup[axis],
                    values[axis == aWritingMode[aLogicalAxis] ? 2 : i]]
    );
    test_computed_values(`${aLogicalAxis} last on single declaration, ` +
                           `'${aWritingModeDecl}'`,
                         `.test { ${decl} }`,
                         expected);
  });

  // (b) with a single physical property after the logical ones

  ["horizontal", "vertical"].forEach(function(aPhysicalAxis) {
    decl = aWritingModeDecl + aGroup.prerequisites +
           `${aGroup.inline}: ${values[0]}; ` +
           `${aGroup.block}: ${values[1]}; ` +
           `${aGroup[aPhysicalAxis]}: ${values[2]}; `;
    var expected = ["inline", "block"].map(
      (axis, i) => [aGroup[aWritingMode[axis]],
                    values[aWritingMode[axis] == aPhysicalAxis ? 2 : i]]
    );
    test_computed_values(`${aPhysicalAxis} last on single declaration, ` +
                           `'${aWritingModeDecl}'`,
                         `.test { ${decl} }`,
                         expected);
  });


  // Test that logical and physical axis properties are cascaded properly when
  // on different declarations.

  var loDecl;  // lower specifity
  var hiDecl;  // higher specificity

  // (a) with a logical property in the high specificity rule

  loDecl = aWritingModeDecl + aGroup.prerequisites +
           `${aGroup.horizontal}: ${values[0]}; ` +
           `${aGroup.vertical}: ${values[1]}; `;

  ["inline", "block"].forEach(function(aLogicalAxis) {
    hiDecl = `${aGroup[aLogicalAxis]}: ${values[2]}; `;
    var expected = ["horizontal", "vertical"].map(
      (axis, i) => [aGroup[axis],
                    values[axis == aWritingMode[aLogicalAxis] ? 2 : i]]
    );
    test_computed_values(`${aLogicalAxis}, two declarations, ` +
                           `'${aWritingModeDecl}'`,
                         `#test { ${hiDecl} } ` +
                         `.test { ${loDecl} }`,
                         expected);
  });

  // (b) with a physical property in the high specificity rule

  loDecl = aWritingModeDecl + aGroup.prerequisites +
           `${aGroup.inline}: ${values[0]}; ` +
           `${aGroup.block}: ${values[1]}; `;

  ["horizontal", "vertical"].forEach(function(aPhysicalAxis) {
    hiDecl = `${aGroup[aPhysicalAxis]}: ${values[2]}; `;
    var expected = ["inline", "block"].map(
      (axis, i) => [aGroup[aWritingMode[axis]],
                    values[aWritingMode[axis] == aPhysicalAxis ? 2 : i]]
    );
    test_computed_values(`${aPhysicalAxis}, two declarations, ` +
                           `'${aWritingModeDecl}'`,
                         `#test { ${hiDecl} } ` +
                         `.test { ${loDecl} }`,
                         expected);
  });
}

function run_box_test_for_writing_mode(aGroup, aWritingMode, aWritingModeDecl) {
  var values = gValues[aGroup.type];
  var decl;

  // Test that logical box properties are converted to their physical
  // equivalent correctly when all four are present on a single
  // declaration, with no overwriting of previous properties and
  // no physical properties present.  We put the writing mode properties
  // on a separate declaration to test that the computed values of these
  // properties are used, rather than those on the same declaration.

  decl = aGroup.prerequisites +
         `${aGroup.inlineStart}: ${values[0]}; ` +
         `${aGroup.inlineEnd}: ${values[1]}; ` +
         `${aGroup.blockStart}: ${values[2]}; ` +
         `${aGroup.blockEnd}: ${values[3]}; `;
  test_computed_values('logical properties on one declaration, writing ' +
                         'mode properties on another, ' +
                         `'${aWritingModeDecl}'`,
                       `.test { ${aWritingModeDecl} } ` +
                       `.test { ${decl} }`,
                       [[aGroup[aWritingMode.inlineStart], values[0]],
                        [aGroup[aWritingMode.inlineEnd],   values[1]],
                        [aGroup[aWritingMode.blockStart],  values[2]],
                        [aGroup[aWritingMode.blockEnd],    values[3]]]);

  // Test that logical and physical box properties are cascaded together,
  // honoring their relative order on a single declaration.

  // (a) with a single logical property after the physical ones

  ["inlineStart", "inlineEnd", "blockStart", "blockEnd"].forEach(function(aLogicalSide) {
    decl = aWritingModeDecl + aGroup.prerequisites +
           `${aGroup.left}: ${values[0]}; ` +
           `${aGroup.right}: ${values[1]}; ` +
           `${aGroup.top}: ${values[2]}; ` +
           `${aGroup.bottom}: ${values[3]}; ` +
           `${aGroup[aLogicalSide]}: ${values[4]}; `;
    var expected = ["left", "right", "top", "bottom"].map(
      (side, i) => [aGroup[side],
                    values[side == aWritingMode[aLogicalSide] ? 4 : i]]
    );
    test_computed_values(`${aLogicalSide} last on single declaration, ` +
                           `'${aWritingModeDecl}'`,
                         `.test { ${decl} }`,
                         expected);
  });

  // (b) with a single physical property after the logical ones

  ["left", "right", "top", "bottom"].forEach(function(aPhysicalSide) {
    decl = aWritingModeDecl + aGroup.prerequisites +
           `${aGroup.inlineStart}: ${values[0]}; ` +
           `${aGroup.inlineEnd}: ${values[1]}; ` +
           `${aGroup.blockStart}: ${values[2]}; ` +
           `${aGroup.blockEnd}: ${values[3]}; ` +
           `${aGroup[aPhysicalSide]}: ${values[4]}; `;
    var expected = ["inlineStart", "inlineEnd", "blockStart", "blockEnd"].map(
      (side, i) => [aGroup[aWritingMode[side]],
                    values[aWritingMode[side] == aPhysicalSide ? 4 : i]]
    );
    test_computed_values(`${aPhysicalSide} last on single declaration, ` +
                           `'${aWritingModeDecl}'`,
                         `.test { ${decl} }`,
                         expected);
  });


  // Test that logical and physical box properties are cascaded properly when
  // on different declarations.

  var loDecl;  // lower specifity
  var hiDecl;  // higher specificity

  // (a) with a logical property in the high specificity rule

  loDecl = aWritingModeDecl + aGroup.prerequisites +
           `${aGroup.left}: ${values[0]}; ` +
           `${aGroup.right}: ${values[1]}; ` +
           `${aGroup.top}: ${values[2]}; ` +
           `${aGroup.bottom}: ${values[3]}; `;

  ["inlineStart", "inlineEnd", "blockStart", "blockEnd"].forEach(function(aLogicalSide) {
    hiDecl = `${aGroup[aLogicalSide]}: ${values[4]}; `;
    var expected = ["left", "right", "top", "bottom"].map(
      (side, i) => [aGroup[side],
                    values[side == aWritingMode[aLogicalSide] ? 4 : i]]
    );
    test_computed_values(`${aLogicalSide}, two declarations, ` +
                           `'${aWritingModeDecl}'`,
                         `#test { ${hiDecl} } ` +
                         `.test { ${loDecl} }`,
                         expected);
  });

  // (b) with a physical property in the high specificity rule

  loDecl = aWritingModeDecl + aGroup.prerequisites +
           `${aGroup.inlineStart}: ${values[0]}; ` +
           `${aGroup.inlineEnd}: ${values[1]}; ` +
           `${aGroup.blockStart}: ${values[2]}; ` +
           `${aGroup.blockEnd}: ${values[3]}; `;

  ["left", "right", "top", "bottom"].forEach(function(aPhysicalSide) {
    hiDecl = `${aGroup[aPhysicalSide]}: ${values[4]}; `;
    var expected = ["inlineStart", "inlineEnd", "blockStart", "blockEnd"].map(
      (side, i) => [aGroup[aWritingMode[side]],
                    values[aWritingMode[side] == aPhysicalSide ? 4 : i]]
    );
    test_computed_values(`${aPhysicalSide}, two declarations, ` +
                           `'${aWritingModeDecl}'`,
                         `#test { ${hiDecl} } ` +
                         `.test { ${loDecl} }`,
                         expected);
  });
}

function run_tests() {
  gBoxPropertyGroups.forEach(function(aGroup) {
    gWritingModes.forEach(function(aWritingMode) {
      aWritingMode.style.forEach(function(aWritingModeDecl) {
        run_box_test_for_writing_mode(aGroup, aWritingMode, aWritingModeDecl);
      });
    });
  });

  gAxisPropertyGroups.forEach(function(aGroup) {
    gWritingModes.forEach(function(aWritingMode) {
      aWritingMode.style.forEach(function(aWritingModeDecl) {
        run_axis_test_for_writing_mode(aGroup, aWritingMode, aWritingModeDecl);
      });
    });
  });

  SimpleTest.finish();
}

SimpleTest.waitForExplicitFinish();
start();
</script>
